﻿Imports System.Collections.ObjectModel
Imports System.ComponentModel.Composition
Imports System.ComponentModel.Composition.Hosting

''' <summary>
''' 表示信息获取器
''' </summary>
''' <remarks></remarks>
Public Class InfoGen
    Implements IDisposable
    Dim _container As CompositionContainer

    Protected _AllLocker As New Object

    ''' <summary>
    ''' 信息提供者集合
    ''' </summary>
    ''' <value>信息提供者集合</value>
    ''' <returns>信息提供者集合</returns>
    ''' <remarks></remarks>
    <ImportMany()>
    Protected Property InfoProvider As ObservableCollection(Of Lazy(Of IInfoProvider, IExtensionCompentMetadata))

    ''' <summary>
    ''' 初始化一个播放器信息获取器
    ''' </summary>
    ''' <param name="addinPath">指定插件搜索目录</param>
    ''' <remarks></remarks>
    Public Sub New(Optional ByVal addinPath As String = Nothing)
        Me.New({addinPath})
    End Sub

    ''' <summary>
    ''' 初始化一个播放器信息获取器
    ''' </summary>
    ''' <param name="addinPaths">指定插件搜索目录</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal addinPaths As IEnumerable(Of String))
        Dim catalog = New AggregateCatalog()
        catalog.Catalogs.Add(New AssemblyCatalog(GetType(InfoGen).Assembly))
        For Each addinPath In addinPaths
            If Not String.IsNullOrEmpty(addinPath) Then
                If IO.Directory.Exists(addinPath) Then
                    catalog.Catalogs.Add(New DirectoryCatalog(addinPath))
                End If
            End If
        Next
        _container = New CompositionContainer(catalog)
        Try
            _container.ComposeParts(Me)
            If InfoProvider IsNot Nothing Then
                For Each addin In InfoProvider
                    addin.Value.StartUp()
                Next
            End If
        Catch ex As Exception
            Debug.WriteLine(ex.ToString)
            Throw
        End Try
    End Sub
    ''' <summary>
    ''' 获取当前支持的播放器列表
    ''' </summary>
    ''' <returns>当前支持的播放器列表</returns>
    ''' <remarks></remarks>
    Public Function GetSupportedPlayers() As List(Of String)
        SyncLock _AllLocker
            Dim PlayerList As New List(Of String)
            Return If(InfoProvider.Count <> 0, InfoProvider.Select(Function(a) a.Metadata.Name).ToList(), PlayerList)
        End SyncLock
    End Function

    ''' <summary>
    ''' 获得当前可用的播放器，但不保证调用<code>GetInfomation</code>时仍然可用
    ''' </summary>
    ''' <returns>当前可用的播放器，但不保证调用<code>GetInfomation</code>时仍然可用</returns>
    ''' <remarks></remarks>
    Public Function GetAvailablePlayer() As List(Of String)
        'Return InfoProvider.Where(
        '           Function(addin) addin.Value.GetAvaibility = True).Select(
        '           Function(addin) addin.Metadata.SupportedPlayer).ToList()
        SyncLock _AllLocker
            Return (From addin In InfoProvider.AsParallel
                    Where addin.Value.GetAvailability = True
                    Select addin.Metadata.Name).ToList
        End SyncLock
    End Function

    ''' <summary>
    ''' 按照特定的播放器优先级获取正在播放信息
    ''' </summary>
    ''' <param name="playerPriority">播放器列表，指示播放器的优先级，靠前者优先</param>
    ''' <returns>获得的正在播放信息</returns>
    ''' <remarks></remarks>
    Public Function GetInfomation(ByVal playerPriority As List(Of String)) As Dictionary(Of String, String)
        SyncLock _AllLocker
            Dim addins = (From addin In InfoProvider.AsParallel()
                         Where addin.Value.GetAvailability = True
                         Order By GetPriority(playerPriority.IndexOf(addin.Metadata.Name))
                         Select InfoProv = addin.Value)
            If addins.Count > 0 Then
                Return addins.ToList.Item(0).GetInfo
            Else
                Return Nothing
            End If
        End SyncLock
    End Function

    ''' <summary>
    ''' 按照默认优先级获取正在播放信息
    ''' </summary>
    ''' <returns>获得的正在播放信息</returns>
    ''' <remarks></remarks>
    Public Function GetInfomation() As Dictionary(Of String, String)
        Return GetInfomation(GetSupportedPlayers)
    End Function

    ''' <summary>
    ''' 将IndexOf返回的-1转为Integer.MaxValue
    ''' </summary>
    ''' <param name="index">Index</param>
    ''' <returns>优先级</returns>
    ''' <remarks></remarks>
    Protected Shared Function GetPriority(ByVal index As Integer) As Integer
        If index = -1 Then
            Return Integer.MaxValue
        Else
            Return index
        End If
    End Function

    ''' <summary>
    ''' 获取所有可获得的信息集合
    ''' </summary>
    ''' <param name="playerPriority">播放器列表，指示播放器的优先级，靠前者优先</param>
    ''' <returns>所有可获得的信息集合</returns>
    ''' <remarks></remarks>
    Public Function GetAllInfomation(ByVal playerPriority As List(Of String)) As Dictionary(Of String, Dictionary(Of String, String))
        SyncLock _AllLocker
            Dim dic As New Dictionary(Of String, Dictionary(Of String, String))
            Dim addins = (From addin In InfoProvider.AsParallel()
                          Let sp = addin.Metadata.Name
                 Where addin.Value.GetAvailability = True AndAlso playerPriority.Contains(sp)
                 Order By GetPriority(playerPriority.IndexOf(sp))
                 Select addin)
            For Each addin In addins
                dic.Add(addin.Metadata.Name, addin.Value.GetInfo)
            Next
            Return dic
        End SyncLock
    End Function

    ''' <summary>
    ''' 按默认的播放器优先级获取所有可获得的信息集合
    ''' </summary>
    ''' <returns>所有可获得的信息集合</returns>
    ''' <remarks></remarks>
    Public Function GetAllInfomation() As Dictionary(Of String, Dictionary(Of String, String))
        Return GetAllInfomation(GetAvailablePlayer())
    End Function

    ''' <summary>
    ''' 获取播放器插件的唯一标识
    ''' </summary>
    ''' <param name="palyerName">指定播放器的名字</param>
    ''' <returns>播放器插件的唯一标识</returns>
    ''' <remarks></remarks>
    Public Function GetPluginID(ByVal palyerName As String) As Long
        Throw New NotImplementedException
    End Function

    Public Sub RescanAddin()
        SyncLock _AllLocker
            If TypeOf _container.Catalog Is AggregateCatalog Then
                Dim ac As AggregateCatalog = _container.Catalog
                ac.Catalogs.OfType(Of DirectoryCatalog).AsParallel.ForAll(Sub(dc) dc.Refresh())
            End If
        End SyncLock
    End Sub

    Public Sub RescanAddin(Directories As IEnumerable(Of String))
        SyncLock _AllLocker
            If TypeOf _container.Catalog Is AggregateCatalog Then
                Dim ac As AggregateCatalog = _container.Catalog

                Dim dics = ac.Catalogs.OfType(Of DirectoryCatalog)()

                Dim dels = dics.Where(Function(c) Not (Directories.Contains(c.Path) OrElse Directories.Contains(c.FullPath)))
                Dim adds = From pa In Directories.AsParallel
                           Let paths = (From p In dics Select p.FullPath)
                           Let paths2 = (From p In dics Select p.Path)
                           Where Not (paths.Contains(pa) OrElse paths2.Contains(pa))
                           Select New DirectoryCatalog(pa)
                For Each c In dels
                    ac.Catalogs.Remove(c)
                Next
                For Each c In adds
                    ac.Catalogs.Add(c)
                Next
                dics.AsParallel.ForAll(Sub(dc) dc.Refresh())
            End If
        End SyncLock
    End Sub

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 检测冗余的调用

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: 释放托管状态(托管对象)。
                SyncLock _AllLocker
                    For Each infop In (From addin In InfoProvider
                                       Where addin.IsValueCreated AndAlso addin.Value IsNot Nothing
                                       Select addin.Value)
                        infop.ShutDown()
                    Next
                    If _container IsNot Nothing Then _container.Dispose()
                End SyncLock
            End If

            ' TODO: 释放非托管资源(非托管对象)并重写下面的 Finalize()。
            ' TODO: 将大型字段设置为 null。
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: 仅当上面的 Dispose(ByVal disposing As Boolean)具有释放非托管资源的代码时重写 Finalize()。
    'Protected Overrides Sub Finalize()
    '    ' 不要更改此代码。请将清理代码放入上面的 Dispose(ByVal disposing As Boolean)中。
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Visual Basic 添加此代码是为了正确实现可处置模式。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' 不要更改此代码。请将清理代码放入上面的 Dispose(ByVal disposing As Boolean)中。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class
